﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Reflection;
using System.ComponentModel;
using Sinacor.Infra.Common;

namespace Sinacor.Infra.Service.Data
{
    /// <summary>
    /// Classe base para acesso a dados
    /// </summary>
    public abstract class BaseResourceAccess : IDisposable
    {
        /// <summary>
        /// Construtor padrão
        /// </summary>
        public BaseResourceAccess()
        {
            _dataBase = new OracleDataBase();
        }

        /// <summary>
        /// Construtor padrão
        /// </summary>
        public BaseResourceAccess(int companyId)
        {
            _dataBase = new OracleDataBase(companyId);
        }

        private OracleDataBase _dataBase;
        /// <summary>
        /// Objeto para acesso ao banco de dados
        /// </summary>
        protected OracleDataBase DataBase
        {
            get { return _dataBase; }
        }

        /// <summary>
        /// Delegate para um método que controi uma entidade
        /// </summary>
        /// <typeparam name="T">Type da entidade</typeparam>
        /// <param name="reader">IDataReader com os dados da entidade</param>
        /// <returns>Objeto com a entidade</returns>
        protected delegate T ConstructEntity<T>(IDataReader reader) where T : new();

        /// <summary>
        /// Executa um commando no bando de dados e retorna uma entidade
        /// </summary>
        /// <typeparam name="T">Type da entidade</typeparam>
        /// <param name="command">Commando a ser executado</param>
        /// <param name="constructEntity">Contrutor da entidade</param>
        /// <returns>Objeto com a entidade</returns>
        protected T FindOne<T>(DbCommand command, ConstructEntity<T> constructEntity) where T : new()
        {
            T entity = default(T);

            using (IDataReader reader = this.DataBase.ExecuteReader(command))
            {
                if (reader.Read())
                {
                    entity = constructEntity(reader);
                }
            }

            return entity;
        }

        [Obsolete("Este método não deve ser utilizado, utilize a sobrecarga que recebe um delegate para construir a entidade. Este método será removido nas próximas versões do framework.")]
        protected T FindOne<T>(DbCommand command) where T : new()//, ConstructEntity<T> constructEntity)
        {
            return this.FindOne<T>(command, new ConstructEntity<T>(PopulateEntity<T>));
        }

        /// <summary>
        /// Executa um commando no banco de dados e retorna uma lista de entidades
        /// </summary>
        /// <typeparam name="T">Type da entidade</typeparam>
        /// <param name="command">Commando a ser executado</param>
        /// <param name="constructEntity">Contrutor da entidade</param>
        /// <returns>Lista de entidades</returns>
        protected List<T> FindAll<T>(DbCommand command, ConstructEntity<T> constructEntity) where T : new()
        {
            T entity = default(T);
            List<T> entities = new List<T>();

            using (IDataReader reader = this.DataBase.ExecuteReader(command))
            {
                while (reader.Read())
                {
                    entity = constructEntity(reader);
                    entities.Add(entity);
                }
            }

            return entities;
        }


        /// <summary>
        /// Executa um commando no banco de dados e retorna uma lista de entidades
        /// </summary>
        /// <typeparam name="T">Type da entidade</typeparam>
        /// <param name="command">Commando a ser executado</param>
        /// <param name="constructEntity">Contrutor da entidade</param>
        /// <returns>Lista de entidades</returns>
        [Obsolete("Este método não deve ser utilizado, utilize a sobrecarga que recebe um delegate para construir a entidade. Este método será removido nas próximas versões do framework.")]
        protected List<T> FindAll<T>(DbCommand command) where T : new()
        {
            return this.FindAll<T>(command, new ConstructEntity<T>(PopulateEntity<T>));
        }

        /// <summary>
        /// Popula membros da classe com o mesmo nome de coluna do DataReader 
        /// </summary>
        /// <param name="preader"></param>
        /// <param name="pInstance"></param>
        private T PopulateEntity<T>(IDataReader reader) where T : new()
        {
            try
            {
                T instance = new T();
                Type oObjectType;
                PropertyInfo oPropertyInfo;
                if (instance == null) return (T)instance;

                oObjectType = instance.GetType();
                int i = 0;
                for (i = 0; i <= (reader.FieldCount - 1); i++)
                {
                    oPropertyInfo = oObjectType.GetProperty(reader.GetName(i));
                    this.SetPropertyValue(instance, reader.GetName(i), reader[i]);
                }
                return instance;
            }
            catch (Exception ex)
            {
                throw new Exception("Erro ao popular a Classe (PopulateClass):" + ex.Message);
            }
        }

        /// <summary>
        /// Atrimui o valor a popriedade
        /// </summary>
        /// <param name="pInstance"></param>
        /// <param name="pPropertyName"></param>
        /// <param name="oValue"></param>
        /// <returns></returns>
        private void SetPropertyValue(object pInstance, string pPropertyName, object pPropertyValue)
        {
            string[] props = pPropertyName.Split(".".ToCharArray());
            for (int i = 0; i < props.Length; i++)
            {
                if (pInstance == null)
                    throw new Exception(" Erro em SetPropertyValue: Propriedade sem instância!");
                PropertyInfo prop = pInstance.GetType().GetProperty(props[i]);
                if (prop == null)
                    break;


                if (i == (props.Length - 1)) //última propriedade
                {
                    pPropertyValue = this.ChangeValueToPropertyType(pPropertyValue, prop);
                    prop.SetValue(pInstance, pPropertyValue, null);
                }
                else
                {
                    //passa para a próxima propriedade
                    pInstance = prop.GetValue(pInstance, null);
                    if (pInstance == null)
                    {
                        pInstance = Activator.CreateInstance(prop.PropertyType);
                    }
                }
            }
        }

        /// Muda o tipo do valor de acordo com a propriedade
        /// </summary>
        /// <param name="property">Propriedade do model</param>
        /// <param name="objValue">Valor da propriedade</param>
        /// <returns>Object tipado para a propriedade</returns>
        private object ChangeValueToPropertyType(object objValue, PropertyInfo property)
        {
            TypeConverter converter = TypeDescriptor.GetConverter(property.PropertyType);
            objValue = objValue == DBNull.Value ? null : objValue;

            if (property.PropertyType.IsEnum)
            {
                return Sinacor.Infra.Service.Common.EnumConverter.Parse(property.PropertyType, Convert.ToString(objValue));
            }
            else if ((property.PropertyType.IsNullable() && objValue == null)
                || (objValue != null && converter.CanConvertFrom(objValue.GetType())))
            {
                return converter.ConvertFrom(objValue);
            }
            else if (converter.CanConvertFrom(typeof(string)))
            {
                return converter.ConvertFrom(Convert.ToString(objValue));
            }

            throw new InvalidCastException("The value " + (objValue != null ? Convert.ToString(objValue) : "null") + " can not be cast to " + property.PropertyType.Name + " type.");
        }

        #region IDisposable Members

        public void Dispose()
        {
            _dataBase.Dispose();
        }

        #endregion
    }
}
